home *** CD-ROM | disk | FTP | other *** search
/ HPAVC / HPAVC CD-ROM.iso / pc / UGPRG.ZIP / DENTHOR / TUT14.DOC < prev    next >
Encoding:
Text File  |  1996-07-27  |  37.9 KB  |  1,264 lines

  1.                    ╒═══════════════════════════════╕
  2.                    │         W E L C O M E         │
  3.                    │  To the VGA Trainer Program   │ │
  4.                    │              By               │ │
  5.                    │      DENTHOR of ASPHYXIA      │ │ │
  6.                    ╘═══════════════════════════════╛ │ │
  7.                      ────────────────────────────────┘ │
  8.                        ────────────────────────────────┘
  9.  
  10.                            --==[ PART 14 ]==--
  11.  
  12.  
  13.  
  14. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  15. ■ Introduction
  16.  
  17. Hello there. Exams are just around the corner (again :( ), so I thought 
  18. I better get round to doing the next trainer. As usual, there seems to 
  19. have been a big delay between this one and the last one... sorry about 
  20. that ;-)
  21.  
  22. Well, this trainer is mainly on four things : Glenzing, faster polys,
  23. fixed point and assembler. The sample program is basically tut 9
  24. rewritten to include the above.
  25.  
  26. I'll go through them in order, and hopefully you won't have any hassles
  27. grasping the concepts. By the way, do any of you read the text files? I
  28. find myself answering questions via E-Mail etc. that were discussed in
  29. the text sections of the trainers ... oh well, I'll just ramble along
  30. anyway ;-)
  31.  
  32. Please dont send any mail to smith9@batis.bis.und.ac.za anymore ... I 
  33. don't know for how much longer the account will be valid (How can a 
  34. non-BIS person get onto the BIS UNIX machine in the BIS2 directory? If 
  35. his name is Denthor I suppose ;-) Oh well, I got about 8 months use out 
  36. of it. The account expires on Christmas day anyway...) So anyway, please
  37. leave all messages to denthor@beastie.cs.und.ac.za
  38.                                                   
  39.  
  40. If you would like to contact me, or the team, there are many ways you
  41. can do it : 1) Write a message to Grant Smith/Denthor/Asphyxia in private mail
  42.                   on the ASPHYXIA BBS.
  43.             2) Write to :  Grant Smith
  44.                            P.O.Box 270 Kloof
  45.                            3640
  46.                            Natal
  47.                            South Africa
  48.             3) Call me (Grant Smith) at (031) 73 2129 (leave a message if you
  49.                   call during varsity). Call +27-31-73-2129 if you call
  50.                   from outside South Africa. (It's YOUR phone bill ;-))
  51.             4) Write to denthor@beastie.cs.und.ac.za in E-Mail.
  52.             5) Write to asphyxia@beastie.cs.und.ac.za to get to all of
  53.                us at once.
  54.  
  55. NB : If you are a representative of a company or BBS, and want ASPHYXIA
  56.        to do you a demo, leave mail to me; we can discuss it.
  57. NNB : If you have done/attempted a demo, SEND IT TO ME! We are feeling
  58.         quite lonely and want to meet/help out/exchange code with other demo
  59.         groups. What do you have to lose? Leave a message here and we can work
  60.         out how to transfer it. We really want to hear from you!
  61.  
  62.  
  63.  
  64. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  65. ■ What is glenzing?
  66.  
  67. This is an easy one. Imagine, in a 3D object, that all the sides are
  68. made out of colored glass. That means that every time you look through
  69. that side, everything behind it is tinged in a certain color.
  70.  
  71. In ascii ...
  72.           +---------+
  73.           |      <--|---Light blue
  74.           |         |
  75.      +--------+     |
  76.      |    | <-|-----|---Dark blue
  77.      |    +---|-----+
  78.      |     <--|---------Light blue
  79.      +--------+
  80.  
  81. So where the two sides overlap, the color values of the two sides are 
  82. added. Easy huh? It is also easy to code. This is how you do it :
  83.  
  84. Set up your pallette to be a nice run of colors.
  85. Draw your first poly.
  86. While drawing poly 1, instead of plonking down a set pixel color, grab the
  87.   backgrond pixel, add 1 to it, then put the result down.
  88. Draw your second poly.
  89. While drawing poly 2, instead of plonking down a set pixel color, grab the
  90.   backgrond pixel, add 2 to it, then put the result down.
  91. and so forth.
  92.  
  93. So if the color behind poly 1 was 5, you would place pixel 6 down
  94. instead.
  95.  
  96. If you do this for every single pixel of every single side of your 3d 
  97. object, you then have glenzing going. This is obviously slightly slower
  98. then just drawing an item straight, but in the sample program it goes
  99. quite quickly ... this is because of the following sections...
  100.   
  101.  
  102. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  103. ■ Faster Polygons
  104.  
  105. In Tut 9, you probably noticed that we were using a multiply for every
  106. single line of the poly that we drew. This is not good. Let's find out
  107. how to speed it up, shall we...
  108.  
  109. With the multiply method, we went through every line, to find out the
  110. minimum x and maximum x value for that line.
  111.  
  112.                            +
  113.                    ------/---\------- Find min x and max x, draw a line
  114.                        /       \        between them.
  115.                      +           +
  116.                        \       /
  117.                          \   /
  118.                            +
  119.  
  120. How about if we found out all the min and max x's for every line first, 
  121. then just went through an array drawing them. We could do it by 
  122. "scanning" each side in turn. Here is how we do it :
  123.  
  124.                           + 1
  125.                         /
  126.                       /
  127.                   2 +
  128.  
  129. We go from point one to point two. For every single y we go down, we 
  130. move a constant x value. This value is found like this :
  131.  
  132.            xchange := (x1-x2)/(y1-y2)
  133.  
  134. Remember gradients? This is how you calulated the slope of a line waaay
  135. back in school. You never thought it would be any use, didn't you ;-)
  136.  
  137. Anyway, with this value, we can do the following :
  138.  
  139.     For loop1:=y1 to y2 do BEGIN
  140.       [ Put clever stuff here ]
  141.       x:=x+xchange;
  142.     END;
  143.     
  144. and we will go through all the x-values we need for that line. Clever, 
  145. huh?
  146.  
  147. Now for the clever bit. You have an array, from 0 to 199 (which is all
  148. the possible y-values your onscreen poly can have). Inside this is two 
  149. values, which will be your min x and your max x. You start off with the 
  150. min x being a huge number, and the max x being a low number. Then you 
  151. scan a side. For each y, check to see if one of the following has 
  152. happened :    If the x value is smaller then the xmin value in your
  153.                 array, make the xmin value equal to the x value
  154.               If the x value is larger then the xmax value in your
  155.                 array, make the xmax value equal to the x value
  156.  
  157. The loop now looks like this :
  158.  
  159.     For loop1:=y1 to y2 do BEGIN
  160.       if x>poly[loop1,1] then poly[loop1,1]:=x;
  161.       if x<poly[loop1,1] then poly[loop1,1]:=x;
  162.       x:=x+xchange;
  163.     END;
  164.  
  165. Easy? Do this for all four sides (you can change this for polys with 
  166. different numbers of sides), and then you have all the x min and x max
  167. values you need to draw your polygon.
  168.  
  169. In the sample program, if you replaced the Hline procedure with one that
  170. draws solid lines, you could use the given drawpoly for solids.
  171.  
  172. Even this procedure is sped up by the next section, on fixed point.
  173.    
  174.  
  175. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  176. ■  What is fixed point?
  177.  
  178. Have you ever noticed how slow reals are? I mean slooooow. You can get a
  179. massive speed increase in most programs by replacing your reals with 
  180. integers, words etc. But, I hear you cry, what happens to the much 
  181. needed fraction bit after the decimal point? The answer? You keep it. 
  182. Here's how.
  183.  
  184. Let us say you have a word, which is 16 bits. If you want to use it as a
  185. fixed point value, you can separate it into 2 sections, one of which 
  186. holds the whole value, and one which holds the fraction.
  187.  
  188.              00000000  00000000   <-Bits
  189.              Whole     Fraction
  190.              
  191. The number 6.5 would therefore be shown as follows :
  192.  
  193.              Top byte    :  6
  194.              Bottom byte :  128
  195.              
  196. 128 is half (or .5) of 256, and in the case of the fraction section, 256
  197. would equal one whole number.
  198.  
  199. So let us say we had 6.5 * 2. Using reals this would be a slow mul, but 
  200. with fixed point ...
  201.  
  202.             Top Byte    :  6     
  203.             Bottom Byte :  128
  204.             Value       :  1664   <-This is the true value of the word
  205.                                      ie. (top byte*256)+bottom byte).
  206.                                      this is how the computer sees the 
  207.                                      word.
  208.              1664 shl 1 = 3328    <-shl 1 is the same as *2, just faster.
  209.             Top byte    :  13
  210.             Bottom byte :  0
  211.  
  212. As you can see, we got the correct result! And in a fraction of the time
  213. that a multiplication of a real would have taken us. You can add and
  214. subtract fixed point values with no hassles, and multiply and divide 
  215. them by normal values too. When you need the whole value section, you 
  216. can just read the high byte, or do the following
  217.  
  218.              whole = word shr 8
  219.          eg  1664 shr 8 = 6
  220.          
  221. As you can see, the fraction is truncated. Obviously, the more bits you 
  222. set aside for the fraction section, the more accurate your calculation 
  223. is, but the lesser the maximum whole number you can have. For example, 
  224. in the above numbers, the maximum value of your whole number was 256, a
  225. far cry from the 65535 a normal (non fixed point) word's maximum.
  226.  
  227. There are a lot of hassles using fixed point (go on, try shift a
  228. negative value), most of which have to do with the fact that you have 
  229. severely decreased the maximum number you may have, but trust me, the 
  230. speed increase is worth it (With longintegers, and/or extended 386
  231. registers, you can even have 16x16 fixed point, which means high
  232. accuracy and high maximum values)
  233.  
  234. Try write a program using fixed point. It is not difficult and you will
  235. get it perfect easily. Trust me, I'm a democoder ;-)
  236.  
  237. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  238. ■ Assembler
  239.  
  240. In the sample program I used one or two assembler commands that I havent
  241. discussed with you ... here they are ...
  242.  
  243.   imul  value      This is the same as mul, but for integer values. It
  244.                      multiplies ax by the value. If the value is a word,
  245.                      it returns the result in DX:AX
  246.  
  247.   sal  register,value   This is the same as shl, but it is arithmetic,
  248.                           in other words it works on integers. If you
  249.                           had to shl a negative value, the result would
  250.                           mean nothing to you.
  251.  
  252.   rcl  register,value   This is the same as shl, but after you have
  253.                           shifted, the value in the carry flag is placed
  254.                           in the now-vacated rightmost bit. The carry
  255.                           flag is set when you do an operation where the
  256.                           result is greater then the upmost possible
  257.                           value of the variable (usually 65535 or 32767)
  258.                           eg mov ax,64000
  259.                              shl ax,1     {<- Carry flag now = 1}
  260.                              
  261. For more info on shifting etc, re-read tut 7, it goes into the concept
  262. in detail.
  263.  
  264. The sample program is basically Tut 9 rewritten. To see how the
  265. assembler stuff is working, do the following ... Go into 50 line mode
  266. (-Much- easier to debug), then hit [Alt - D] then R. A little box with
  267. all your registers, segments etc and their values will pop up. Move it
  268. down to where you want it, then go back to the program screen (Hit Alt
  269. and it's number together), and resize it so that you have both it and
  270. the register box onscreen at once (Alt - 5 to resize) ... then use F4,
  271. F7 and F8 to trace though the program (you know how). The current value
  272. of the registers will always be in that box.
  273.  
  274.  
  275. =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  276. ■ In closing
  277.  
  278. Well, that is about it. The sample program may start as being a little 
  279. intimidating to some when they first look at it, just remember to read 
  280. it with tut 9, very little is different, it's just with fixed point and 
  281. a bit of assembler.
  282.  
  283. Before I forget, with Tut 13, the program crashes if you have error 
  284. checking on. This is how you sort it out :
  285.  
  286. 1) Turn off error checking     or
  287. 2) Make logo a pointer, and get and free the memory     or
  288. 3) Read the logo directly to screen     or
  289. 4) Use the {$M ......} command with various values at the top of the 
  290.      program till it works.
  291.      
  292. I prefer options 3 or 2, but hey ... the problem was that the logo was
  293. rather large (16k), and Pascal likes complaining ;-)
  294.  
  295. I am in doubt as to weather to continue doing quotes ... here is a 
  296. conversation I had with Pipsy, after the group conversation got around 
  297. to weather we were normal or not ...
  298.  
  299. Me : I'm normal.
  300. Pipsy : No your not.
  301. Me : Prove it.
  302. Pipsy : Just look at your quotes in your trainers.
  303. Me : What? You think those are weird?
  304. Pipsy : Too weird.
  305. Me : You mean that there is a weirdness line, and I crossed it?
  306. Pipsy : Yes.
  307.  
  308. Bit of a conversation killer that, so we stopped there.
  309.  
  310. Anyway, this trainer won't have a quote in it ... how about a disclaimer
  311. instead? Feel free to use it in your messages ...
  312.  
  313. ------------------------------------------------------------------------
  314. The views expressed above are mine and not Novells. In fact, I've never
  315. worked for them in my life!
  316.  
  317. Byeeee....
  318.   - Denthor
  319.       18:57
  320.         9-9-94
  321.  
  322.  
  323. The following are official ASPHYXIA distribution sites :
  324.  
  325. ╔══════════════════════════╦════════════════╦═════╗
  326. ║BBS Name                  ║Telephone No.   ║Open ║
  327. ╠══════════════════════════╬════════════════╬═════╣
  328. ║ASPHYXIA BBS #1           ║+27-31-765-5312 ║ALL  ║
  329. ║ASPHYXIA BBS #2           ║+27-31-765-6293 ║ALL  ║
  330. ║C-Spam BBS                ║410-531-5886    ║ALL  ║
  331. ║POP!                      ║+27-12-661-1257 ║ALL  ║
  332. ║Soul Asylum               ║+358-0-5055041  ║ALL  ║
  333. ║Wasted Image              ║407-838-4525    ║ALL  ║
  334. ╚══════════════════════════╩════════════════╩═════╝
  335.  
  336. Leave me mail if you want to become an official Asphyxia BBS
  337. distribution site.
  338. Unit GFX2;
  339.  
  340.  
  341. INTERFACE
  342.  
  343. USES crt;
  344. CONST VGA = $A000;
  345.  
  346. TYPE Virtual = Array [1..64000] of byte;  { The size of our Virtual Screen }
  347.      VirtPtr = ^Virtual;                  { Pointer to the virtual screen }
  348.  
  349. VAR Virscr : VirtPtr;                     { Our first Virtual screen }
  350.     Vaddr  : word;                        { The segment of our virtual screen}
  351.  
  352. Procedure SetMCGA;
  353.    { This procedure gets you into 320x200x256 mode. }
  354. Procedure SetText;
  355.    { This procedure returns you to text mode.  }
  356. Procedure Cls (Where:word;Col : Byte);
  357.    { This clears the screen to the specified color }
  358. Procedure SetUpVirtual;
  359.    { This sets up the memory needed for the virtual screen }
  360. Procedure ShutDown;
  361.    { This frees the memory used by the virtual screen }
  362. procedure flip(source,dest:Word);
  363.    { This copies the entire screen at "source" to destination }
  364. Procedure Pal(Col,R,G,B : Byte);
  365.    { This sets the Red, Green and Blue values of a certain color }
  366. Procedure GetPal(Col : Byte; Var R,G,B : Byte);
  367.   { This gets the Red, Green and Blue values of a certain color }
  368. procedure WaitRetrace;
  369.    {  This waits for a vertical retrace to reduce snow on the screen }
  370. Procedure Hline (x1,x2,y:word;col:byte;where:word);
  371.    { This draws a horizontal line from x1 to x2 on line y in color col }
  372. Procedure Line(a,b,c,d:integer;col:byte;where:word);
  373.   { This draws a solid line from a,b to c,d in colour col }
  374. Procedure DrawPoly(x1,y1,x2,y2,x3,y3,x4,y4:integer;color:byte;where:word);
  375.    { This draw a polygon with 4 points at x1,y1 , x2,y2 , x3,y3 , x4,y4
  376.      in color col }
  377. Function rad (theta : real) : real;
  378.    {  This calculates the degrees of an angle }
  379. Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);
  380.    { This puts a pixel on the screen by writing directly to memory. }
  381. Function Getpixel (X,Y : Integer; where:word) :Byte;
  382.    { This gets the pixel on the screen by reading directly to memory. }
  383. Procedure LoadCEL (FileName :  string; ScrPtr : pointer);
  384.   { This loads the cel 'filename' into the pointer scrptr }
  385.  
  386.  
  387. IMPLEMENTATION
  388.  
  389. {──────────────────────────────────────────────────────────────────────────}
  390. Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
  391. BEGIN
  392.   asm
  393.      mov        ax,0013h
  394.      int        10h
  395.   end;
  396. END;
  397.  
  398. {──────────────────────────────────────────────────────────────────────────}
  399. Procedure SetText;  { This procedure returns you to text mode.  }
  400. BEGIN
  401.   asm
  402.      mov        ax,0003h
  403.      int        10h
  404.   end;
  405. END;
  406.  
  407. {──────────────────────────────────────────────────────────────────────────}
  408. Procedure Cls (Where:word;Col : Byte); assembler;
  409.    { This clears the screen to the specified color }
  410. asm
  411.    push    es
  412.    mov     cx, 32000;
  413.    mov     es,[where]
  414.    xor     di,di
  415.    mov     al,[col]
  416.    mov     ah,al
  417.    rep     stosw
  418.    pop     es
  419. End;
  420.  
  421. {──────────────────────────────────────────────────────────────────────────}
  422. Procedure SetUpVirtual;
  423.    { This sets up the memory needed for the virtual screen }
  424. BEGIN
  425.   GetMem (VirScr,64000);
  426.   vaddr := seg (virscr^);
  427. END;
  428.  
  429. {──────────────────────────────────────────────────────────────────────────}
  430. Procedure ShutDown;
  431.    { This frees the memory used by the virtual screen }
  432. BEGIN
  433.   FreeMem (VirScr,64000);
  434. END;
  435.  
  436. {──────────────────────────────────────────────────────────────────────────}
  437. procedure flip(source,dest:Word); assembler;
  438.   { This copies the entire screen at "source" to destination }
  439. asm
  440.   push    ds
  441.   mov     ax, [Dest]
  442.   mov     es, ax
  443.   mov     ax, [Source]
  444.   mov     ds, ax
  445.   xor     si, si
  446.   xor     di, di
  447.   mov     cx, 32000
  448.   rep     movsw
  449.   pop     ds
  450. end;
  451.  
  452. {──────────────────────────────────────────────────────────────────────────}
  453. Procedure Pal(Col,R,G,B : Byte); assembler;
  454.   { This sets the Red, Green and Blue values of a certain color }
  455. asm
  456.    mov    dx,3c8h
  457.    mov    al,[col]
  458.    out    dx,al
  459.    inc    dx
  460.    mov    al,[r]
  461.    out    dx,al
  462.    mov    al,[g]
  463.    out    dx,al
  464.    mov    al,[b]
  465.    out    dx,al
  466. end;
  467.  
  468. {──────────────────────────────────────────────────────────────────────────}
  469. Procedure GetPal(Col : Byte; Var R,G,B : Byte);
  470.   { This gets the Red, Green and Blue values of a certain color }
  471. Var
  472.    rr,gg,bb : Byte;
  473. Begin
  474.    asm
  475.       mov    dx,3c7h
  476.       mov    al,col
  477.       out    dx,al
  478.  
  479.       add    dx,2
  480.  
  481.       in     al,dx
  482.       mov    [rr],al
  483.       in     al,dx
  484.       mov    [gg],al
  485.       in     al,dx
  486.       mov    [bb],al
  487.    end;
  488.    r := rr;
  489.    g := gg;
  490.    b := bb;
  491. end;
  492.  
  493. {──────────────────────────────────────────────────────────────────────────}
  494. procedure WaitRetrace; assembler;
  495.   {  This waits for a vertical retrace to reduce snow on the screen }
  496. label
  497.   l1, l2;
  498. asm
  499.     mov dx,3DAh
  500. l1:
  501.     in al,dx
  502.     and al,08h
  503.     jnz l1
  504. l2:
  505.     in al,dx
  506.     and al,08h
  507.     jz  l2
  508. end;
  509.  
  510. {──────────────────────────────────────────────────────────────────────────}
  511. Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler;
  512.   { This draws a horizontal line from x1 to x2 on line y in color col }
  513. asm
  514.   mov   ax,where
  515.   mov   es,ax
  516.   mov   ax,y
  517.   mov   di,ax
  518.   shl   ax,8
  519.   shl   di,6
  520.   add   di,ax
  521.   add   di,x1
  522.  
  523.   mov   al,col
  524.   mov   ah,al
  525.   mov   cx,x2
  526.   sub   cx,x1
  527.   shr   cx,1
  528.   jnc   @start
  529.   stosb
  530. @Start :
  531.   rep   stosw
  532. end;
  533.  
  534. {──────────────────────────────────────────────────────────────────────────}
  535. Procedure Line(a,b,c,d:integer;col:byte;where:word);
  536.   { This draws a solid line from a,b to c,d in colour col }
  537.   function sgn(a:real):integer;
  538.   begin
  539.        if a>0 then sgn:=+1;
  540.        if a<0 then sgn:=-1;
  541.        if a=0 then sgn:=0;
  542.   end;
  543. var i,s,d1x,d1y,d2x,d2y,u,v,m,n:integer;
  544. begin
  545.      u:= c - a;
  546.      v:= d - b;
  547.      d1x:= SGN(u);
  548.      d1y:= SGN(v);
  549.      d2x:= SGN(u);
  550.      d2y:= 0;
  551.      m:= ABS(u);
  552.      n := ABS(v);
  553.      IF NOT (M>N) then
  554.      BEGIN
  555.           d2x := 0 ;
  556.           d2y := SGN(v);
  557.           m := ABS(v);
  558.           n := ABS(u);
  559.      END;
  560.      s := m shr 1;
  561.      FOR i := 0 TO m DO
  562.      BEGIN
  563.           putpixel(a,b,col,where);
  564.           s := s + n;
  565.           IF not (s<m) THEN
  566.           BEGIN
  567.                s := s - m;
  568.                a:= a + d1x;
  569.                b := b + d1y;
  570.           END
  571.           ELSE
  572.           BEGIN
  573.                a := a + d2x;
  574.                b := b + d2y;
  575.           END;
  576.      end;
  577. END;
  578.  
  579.  
  580. {──────────────────────────────────────────────────────────────────────────}
  581. Procedure DrawPoly(x1,y1,x2,y2,x3,y3,x4,y4:integer;color:byte;where:word);
  582.   { This draw a polygon with 4 points at x1,y1 , x2,y2 , x3,y3 , x4,y4
  583.     in color col }
  584. var
  585.   x:integer;
  586.   mny,mxy:integer;
  587.   mnx,mxx,yc:integer;
  588.   mul1,div1,
  589.   mul2,div2,
  590.   mul3,div3,
  591.   mul4,div4:integer;
  592.  
  593. begin
  594.   mny:=y1; mxy:=y1;
  595.   if y2<mny then mny:=y2;
  596.   if y2>mxy then mxy:=y2;
  597.   if y3<mny then mny:=y3;
  598.   if y3>mxy then mxy:=y3;    { Choose the min y mny and max y mxy }
  599.   if y4<mny then mny:=y4;
  600.   if y4>mxy then mxy:=y4;
  601.  
  602.   if mny<0 then mny:=0;
  603.   if mxy>199 then mxy:=199;
  604.   if mny>199 then exit;
  605.   if mxy<0 then exit;        { Verticle range checking }
  606.  
  607.   mul1:=x1-x4; div1:=y1-y4;
  608.   mul2:=x2-x1; div2:=y2-y1;
  609.   mul3:=x3-x2; div3:=y3-y2;
  610.   mul4:=x4-x3; div4:=y4-y3;  { Constansts needed for intersection calc }
  611.  
  612.   for yc:=mny to mxy do
  613.     begin
  614.       mnx:=320;
  615.       mxx:=-1;
  616.       if (y4>=yc) or (y1>=yc) then
  617.         if (y4<=yc) or (y1<=yc) then   { Check that yc is between y1 and y4 }
  618.           if not(y4=y1) then
  619.             begin
  620.               x:=(yc-y4)*mul1 div div1+x4; { Point of intersection on x axis }
  621.               if x<mnx then
  622.                 mnx:=x;
  623.               if x>mxx then
  624.                 mxx:=x;       { Set point as start or end of horiz line }
  625.             end;
  626.       if (y1>=yc) or (y2>=yc) then
  627.         if (y1<=yc) or (y2<=yc) then   { Check that yc is between y1 and y2 }
  628.           if not(y1=y2) then
  629.             begin
  630.               x:=(yc-y1)*mul2 div div2+x1; { Point of intersection on x axis }
  631.               if x<mnx then
  632.                 mnx:=x;
  633.               if x>mxx then
  634.                 mxx:=x;       { Set point as start or end of horiz line }
  635.             end;
  636.       if (y2>=yc) or (y3>=yc) then
  637.         if (y2<=yc) or (y3<=yc) then   { Check that yc is between y2 and y3 }
  638.           if not(y2=y3) then
  639.             begin
  640.               x:=(yc-y2)*mul3 div div3+x2; { Point of intersection on x axis }
  641.               if x<mnx then
  642.                 mnx:=x;
  643.               if x>mxx then
  644.                 mxx:=x;       { Set point as start or end of horiz line }
  645.             end;
  646.       if (y3>=yc) or (y4>=yc) then
  647.         if (y3<=yc) or (y4<=yc) then   { Check that yc is between y3 and y4 }
  648.           if not(y3=y4) then
  649.             begin
  650.               x:=(yc-y3)*mul4 div div4+x3; { Point of intersection on x axis }
  651.               if x<mnx then
  652.                 mnx:=x;
  653.               if x>mxx then
  654.                 mxx:=x;       { Set point as start or end of horiz line }
  655.             end;
  656.       if mnx<0 then
  657.         mnx:=0;
  658.       if mxx>319 then
  659.         mxx:=319;          { Range checking on horizontal line }
  660.       if mnx<=mxx then
  661.         hline (mnx,mxx,yc,color,where);   { Draw the horizontal line }
  662.     end;
  663.   end;
  664.  
  665. {──────────────────────────────────────────────────────────────────────────}
  666. Function rad (theta : real) : real;
  667.   {  This calculates the degrees of an angle }
  668. BEGIN
  669.   rad := theta * pi / 180
  670. END;
  671.  
  672. {──────────────────────────────────────────────────────────────────────────}
  673. Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); assembler;
  674.   { This puts a pixel on the screen by writing directly to memory. }
  675. Asm
  676.   mov     ax,[where]
  677.   mov     es,ax
  678.   mov     bx,[X]
  679.   mov     dx,[Y]
  680.   mov     di,bx
  681.   mov     bx, dx                  {; bx = dx}
  682.   shl     dx, 8
  683.   shl     bx, 6
  684.   add     dx, bx                  {; dx = dx + bx (ie y*320)}
  685.   add     di, dx                  {; finalise location}
  686.   mov     al, [Col]
  687.   stosb
  688. End;
  689.  
  690. {──────────────────────────────────────────────────────────────────────────}
  691. Function Getpixel (X,Y : Integer; where:word):byte; assembler;
  692.   { This puts a pixel on the screen by writing directly to memory. }
  693. Asm
  694.   mov     ax,[where]
  695.   mov     es,ax
  696.   mov     bx,[X]
  697.   mov     dx,[Y]
  698.   mov     di,bx
  699.   mov     bx, dx                  {; bx = dx}
  700.   shl     dx, 8
  701.   shl     bx, 6
  702.   add     dx, bx                  {; dx = dx + bx (ie y*320)}
  703.   add     di, dx                  {; finalise location}
  704.   mov     al, es:[di]
  705. End;
  706.  
  707. {──────────────────────────────────────────────────────────────────────────}
  708. Procedure LoadCEL (FileName :  string; ScrPtr : pointer);
  709.   { This loads the cel 'filename' into the pointer scrptr }
  710. var
  711.   Fil : file;
  712.   Buf : array [1..1024] of byte;
  713.   BlocksRead, Count : word;
  714. begin
  715.   assign (Fil, FileName);
  716.   reset (Fil, 1);
  717.   BlockRead (Fil, Buf, 800);    { Read and ignore the 800 byte header }
  718.   Count := 0; BlocksRead := $FFFF;
  719.   while (not eof (Fil)) and (BlocksRead <> 0) do begin
  720.     BlockRead (Fil, mem [seg (ScrPtr^): ofs (ScrPtr^) + Count], 1024, BlocksRead);
  721.     Count := Count + 1024;
  722.   end;
  723.   close (Fil);
  724. end;
  725.  
  726.  
  727.  
  728.  
  729. BEGIN
  730. END.{$X+}
  731. USES Crt,GFX2;
  732.  
  733. CONST VGA = $A000;
  734.       maxpolys = 6;
  735.       A : Array [1..maxpolys,1..4,1..3] of integer =
  736.         (
  737.          ((-10,-10,10),(-10,10,10),(10,10,10),(10,-10,10)),
  738.          ((-10,-10,-10),(-10,10,-10),(10,10,-10),(10,-10,-10)),
  739.          ((-10,-10,-10),(-10,10,-10),(-10,10,10),(-10,-10,10)),
  740.          ((10,-10,-10),(10,10,-10),(10,10,10),(10,-10,10)),
  741.          ((10,-10,10),(10,-10,-10),(-10,-10,-10),(-10,-10,10)),
  742.          ((10,10,10),(10,10,-10),(-10,10,-10),(-10,10,10))
  743.         );  { The 3-D coordinates of our object ... stored as (X1,Y1,Z1), }
  744.             { (X2,Y2,Z2) ... for the 4 points of a poly }
  745.  
  746.  
  747. Type Point = Record
  748.                x,y,z:integer;                { The data on every point we rotate}
  749.              END;
  750.  
  751.  
  752. VAR Lines : Array [1..maxpolys,1..4] of Point; { The base object to be rotated }
  753.     Translated : Array [1..maxpolys,1..4] of Point; { The rotated object }
  754.     lookup : Array [0..360,1..2] of integer; { Our sin and cos lookup table }
  755.     poly : array [0..199,1..2] of integer;
  756.     ytopclip,ybotclip:integer;  {where to clip our polys to}
  757.     xoff,yoff,zoff:integer;
  758.  
  759.  
  760. {──────────────────────────────────────────────────────────────────────────}
  761. Procedure SetMCGA;  { This procedure gets you into 320x200x256 mode. }
  762. BEGIN
  763.   asm
  764.      mov        ax,0013h
  765.      int        10h
  766.   end;
  767. END;
  768.  
  769.  
  770. {──────────────────────────────────────────────────────────────────────────}
  771. Procedure Hline (x1,x2,y:word;col:byte;where:word); assembler;
  772.   { This draws a horizontal line from x1 to x2 on line y in color col }
  773. asm
  774.   mov   ax,where
  775.   mov   es,ax
  776.   mov   ax,y
  777.   mov   di,ax
  778.   shl   ax,8
  779.   shl   di,6
  780.   add   di,ax
  781.   add   di,x1
  782.  
  783.   mov   cx,x2
  784.   sub   cx,x1
  785.   cmp   cx,0
  786.   jle   @End
  787. @Loop1 :
  788.   mov   al,es:[di]
  789.   add   al,col
  790. {  inc   al}
  791.   stosb
  792.   loop  @loop1
  793. @End:
  794. end;
  795.  
  796.  
  797. {──────────────────────────────────────────────────────────────────────────}
  798. Procedure DrawPoly(x1,y1,x2,y2,x3,y3,x4,y4:integer;color:byte;where:word);
  799.   { This draw a polygon with 4 points at x1,y1 , x2,y2 , x3,y3 , x4,y4
  800.     in color col }
  801. var miny,maxy:integer;
  802.     loop1:integer;
  803.  
  804. Procedure doside (x1,y1,x2,y2:integer);
  805.   { This scans the side of a polygon and updates the poly variable }
  806. VAR temp:integer;
  807.     x,xinc:integer;
  808.     loop1:integer;
  809. BEGIN
  810.   if y1=y2 then exit;
  811.   if y2<y1 then BEGIN
  812.     temp:=y2;
  813.     y2:=y1;
  814.     y1:=temp;
  815.     temp:=x2;
  816.     x2:=x1;
  817.     x1:=temp;
  818.   END;
  819.   xinc:=((x2-x1) shl 7) div (y2-y1);
  820.   x:=x1 shl 7;
  821.   for loop1:=y1 to y2 do BEGIN
  822.     if (loop1>ytopclip-1) and (loop1<ybotclip+1) then BEGIN
  823.       if (x shr 7<poly[loop1,1]) then poly[loop1,1]:=x shr 7;
  824.       if (x shr 7>poly[loop1,2]) then poly[loop1,2]:=x shr 7;
  825.     END;
  826.     x:=x+xinc;
  827.   END;
  828. END;
  829.  
  830. begin
  831.   asm
  832.     mov   si,offset poly
  833.     mov   cx,200
  834. @Loop1:
  835.     mov   ax,32766
  836.     mov   ds:[si],ax
  837.     inc   si
  838.     inc   si
  839.     mov   ax,-32767
  840.     mov   ds:[si],ax
  841.     inc   si
  842.     inc   si
  843.     loop  @loop1
  844.   end;     { Setting the minx and maxx values to extremes }
  845.   miny:=y1;
  846.   maxy:=y1;
  847.   if y2<miny then miny:=y2;
  848.   if y3<miny then miny:=y3;
  849.   if y4<miny then miny:=y4;
  850.   if y2>maxy then maxy:=y2;
  851.   if y3>maxy then maxy:=y3;
  852.   if y4>maxy then maxy:=y4;
  853.   if miny<ytopclip then miny:=ytopclip;
  854.   if maxy>ybotclip then maxy:=ybotclip;
  855.   if (miny>199) or (maxy<0) then exit;
  856.  
  857.   Doside (x1,y1,x2,y2);
  858.   Doside (x2,y2,x3,y3);
  859.   Doside (x3,y3,x4,y4);
  860.   Doside (x4,y4,x1,y1);
  861.  
  862.   for loop1:= miny to maxy do
  863.     hline (poly[loop1,1],poly[loop1,2],loop1,color,where);
  864. end;
  865.  
  866.  
  867. {──────────────────────────────────────────────────────────────────────────}
  868. Procedure SetUpPoints;
  869.   { This creates the lookup table }
  870. VAR loop1,loop2:integer;
  871. BEGIN
  872.   For loop1:=0 to 360 do BEGIN
  873.     lookup [loop1,1]:=round(sin (rad (loop1))*16384);
  874.     lookup [loop1,2]:=round(cos (rad (loop1))*16384);
  875.   END;
  876. END;
  877.  
  878.  
  879. {──────────────────────────────────────────────────────────────────────────}
  880. Procedure RotatePoints (x,Y,z:Integer);
  881.   { This rotates the objecct in lines to translated }
  882. VAR loop1,loop2:integer;
  883.     a,b,c:integer;
  884. BEGIN
  885.   For loop1:=1 to maxpolys do BEGIN
  886.     for loop2:=1 to 4 do BEGIN
  887.       b:=lookup[y,2];
  888.       c:=lines[loop1,loop2].x;
  889.       asm
  890.         mov   ax,b
  891.         imul  c
  892.         sal   ax,1
  893.         rcl   dx,1
  894.         sal   ax,1
  895.         rcl   dx,1
  896.         mov   a,dx
  897.       end;
  898.       b:=lookup[y,1];
  899.       c:=lines[loop1,loop2].z;
  900.       asm
  901.         mov   ax,b
  902.         imul  c
  903.         sal   ax,1
  904.         rcl   dx,1
  905.         sal   ax,1
  906.         rcl   dx,1
  907.         add   a,dx
  908.       end;
  909.       translated[loop1,loop2].x:=a;
  910.       translated[loop1,loop2].y:=lines[loop1,loop2].y;
  911.       b:=-lookup[y,1];
  912.       c:=lines[loop1,loop2].x;
  913.       asm
  914.         mov   ax,b
  915.         imul  c
  916.         sal   ax,1
  917.         rcl   dx,1
  918.         sal   ax,1
  919.         rcl   dx,1
  920.         mov   a,dx
  921.       end;
  922.       b:=lookup[y,2];
  923.       c:=lines[loop1,loop2].z;
  924.       asm
  925.         mov   ax,b
  926.         imul  c
  927.         sal   ax,1
  928.         rcl   dx,1
  929.         sal   ax,1
  930.         rcl   dx,1
  931.         add   a,dx
  932.       end;
  933.       translated[loop1,loop2].z:=a;
  934.  
  935.  
  936.       if x<>0 then BEGIN
  937.         b:=lookup[x,2];
  938.         c:=translated[loop1,loop2].y;
  939.         asm
  940.           mov   ax,b
  941.           imul  c
  942.           sal   ax,1
  943.           rcl   dx,1
  944.           sal   ax,1
  945.           rcl   dx,1
  946.           mov   a,dx
  947.         end;
  948.         b:=lookup[x,1];
  949.         c:=translated[loop1,loop2].z;
  950.         asm
  951.           mov   ax,b
  952.           imul  c
  953.           sal   ax,1
  954.           rcl   dx,1
  955.           sal   ax,1
  956.           rcl   dx,1
  957.           sub   a,dx
  958.         end;
  959.         b:=lookup[x,1];
  960.         c:=translated[loop1,loop2].y;
  961.         translated[loop1,loop2].y:=a;
  962.         asm
  963.           mov   ax,b
  964.           imul  c
  965.           sal   ax,1
  966.           rcl   dx,1
  967.           sal   ax,1
  968.           rcl   dx,1
  969.           mov   a,dx
  970.         end;
  971.         b:=lookup[x,2];
  972.         c:=translated[loop1,loop2].z;
  973.         asm
  974.           mov   ax,b
  975.           imul  c
  976.           sal   ax,1
  977.           rcl   dx,1
  978.           sal   ax,1
  979.           rcl   dx,1
  980.           add   a,dx
  981.         end;
  982.         translated[loop1,loop2].z:=a;
  983.       END;
  984.  
  985.  
  986.  
  987.  
  988.       if z<>0 then BEGIN
  989.         b:=lookup[z,2];
  990.         c:=translated[loop1,loop2].x;
  991.         asm
  992.           mov   ax,b
  993.           imul  c
  994.           sal   ax,1
  995.           rcl   dx,1
  996.           sal   ax,1
  997.           rcl   dx,1
  998.           mov   a,dx
  999.         end;
  1000.         b:=lookup[z,1];
  1001.         c:=translated[loop1,loop2].y;
  1002.         asm
  1003.           mov   ax,b
  1004.           imul  c
  1005.           sal   ax,1
  1006.           rcl   dx,1
  1007.           sal   ax,1
  1008.           rcl   dx,1
  1009.           sub   a,dx
  1010.         end;
  1011.         b:=lookup[z,1];
  1012.         c:=translated[loop1,loop2].x;
  1013.         translated[loop1,loop2].x:=a;
  1014.         asm
  1015.           mov   ax,b
  1016.           imul  c
  1017.           sal   ax,1
  1018.           rcl   dx,1
  1019.           sal   ax,1
  1020.           rcl   dx,1
  1021.           mov   a,dx
  1022.         end;
  1023.         b:=lookup[z,2];
  1024.         c:=translated[loop1,loop2].y;
  1025.         asm
  1026.           mov   ax,b
  1027.           imul  c
  1028.           sal   ax,1
  1029.           rcl   dx,1
  1030.           sal   ax,1
  1031.           rcl   dx,1
  1032.           add   a,dx
  1033.         end;
  1034.         translated[loop1,loop2].y:=a;
  1035.       END;
  1036.     END;
  1037.   END;
  1038. END;
  1039.  
  1040.  
  1041.  
  1042. {──────────────────────────────────────────────────────────────────────────}
  1043. Procedure DrawPoints;
  1044.   { This draws the translated object to the virtual screen }
  1045. VAR loop1:Integer;
  1046.     temp:integer;
  1047.     nx:integer;
  1048.     tx1,ty1,tx2,ty2,tx3,ty3,tx4,ty4:integer;
  1049. BEGIN
  1050.   For loop1:=1 to maxpolys do BEGIN
  1051.     If (translated[loop1,1].z+zoff<0) and (translated[loop1,2].z+zoff<0)
  1052.        and (translated[loop1,3].z+zoff<0) and (translated[loop1,4].z+zoff<0)
  1053.        then BEGIN
  1054.       temp:=round (translated[loop1,1].z)+zoff;
  1055.       nx:=translated[loop1,1].X;
  1056.       asm
  1057.         mov   ax,nx
  1058.         mov   dx,ax
  1059.         sal   ax,8
  1060.         sar   dx,8
  1061.         idiv  temp
  1062.         add   ax,160
  1063.         mov   nx,ax
  1064.       end;
  1065.       tx1:=nx;
  1066.       nx:=translated[loop1,1].Y;
  1067.       asm
  1068.         mov   ax,nx
  1069.         mov   dx,ax
  1070.         sal   ax,8
  1071.         sar   dx,8
  1072.         idiv  temp
  1073.         add   ax,100
  1074.         mov   nx,ax
  1075.       end;
  1076.       ty1:=nx;
  1077.  
  1078.  
  1079.       temp:=round (translated[loop1,2].z)+zoff;
  1080.       nx:=translated[loop1,2].X;
  1081.       asm
  1082.         mov   ax,nx
  1083.         mov   dx,ax
  1084.         sal   ax,8
  1085.         sar   dx,8
  1086.         idiv  temp
  1087.         add   ax,160
  1088.         mov   nx,ax
  1089.       end;
  1090.       tx2:=nx;
  1091.       nx:=translated[loop1,2].Y;
  1092.       asm
  1093.         mov   ax,nx
  1094.         mov   dx,ax
  1095.         sal   ax,8
  1096.         sar   dx,8
  1097.         idiv  temp
  1098.         add   ax,100
  1099.         mov   nx,ax
  1100.       end;
  1101.       ty2:=nx;
  1102.  
  1103.  
  1104.       temp:=round (translated[loop1,3].z)+zoff;
  1105.       nx:=translated[loop1,3].X;
  1106.       asm
  1107.         mov   ax,nx
  1108.         mov   dx,ax
  1109.         sal   ax,8
  1110.         sar   dx,8
  1111.         idiv  temp
  1112.         add   ax,160
  1113.         mov   nx,ax
  1114.       end;
  1115.       tx3:=nx;
  1116.       nx:=translated[loop1,3].Y;
  1117.       asm
  1118.         mov   ax,nx
  1119.         mov   dx,ax
  1120.         sal   ax,8
  1121.         sar   dx,8
  1122.         idiv  temp
  1123.         add   ax,100
  1124.         mov   nx,ax
  1125.       end;
  1126.       ty3:=nx;
  1127.  
  1128.  
  1129.       temp:=round (translated[loop1,4].z)+zoff;
  1130.       nx:=translated[loop1,4].X;
  1131.       asm
  1132.         mov   ax,nx
  1133.         mov   dx,ax
  1134.         sal   ax,8
  1135.         sar   dx,8
  1136.         idiv  temp
  1137.         add   ax,160
  1138.         mov   nx,ax
  1139.       end;
  1140.       tx4:=nx;
  1141.       nx:=translated[loop1,4].Y;
  1142.       asm
  1143.         mov   ax,nx
  1144.         mov   dx,ax
  1145.         sal   ax,8
  1146.         sar   dx,8
  1147.         idiv  temp
  1148.         add   ax,100
  1149.         mov   nx,ax
  1150.       end;
  1151.       ty4:=nx;
  1152.  
  1153.       drawpoly (tx1,ty1,tx2,ty2,tx3,ty3,tx4,ty4,loop1,vaddr);
  1154.     END;
  1155.   END;
  1156. END;
  1157.  
  1158.  
  1159. {──────────────────────────────────────────────────────────────────────────}
  1160. Procedure MoveAround;
  1161.   { This is the main display procedure. }
  1162. VAR deg,loop1,loop2:integer;
  1163.     ch:char;
  1164.  
  1165. BEGIN
  1166.   for loop1:=1 to 15 do
  1167.     pal (loop1,0,loop1*4+3,63-(loop1*4+3));
  1168.   pal (100,50,50,50);
  1169.  
  1170.   deg:=0;
  1171.   ch:=#0;
  1172.   Cls (vaddr,0);
  1173.   For loop1:=1 to maxpolys do
  1174.     For loop2:=1 to 4 do BEGIN
  1175.       Lines [loop1,loop2].x:=a [loop1,loop2,1]*8;
  1176.       Lines [loop1,loop2].y:=a [loop1,loop2,2]*8;
  1177.       Lines [loop1,loop2].z:=a [loop1,loop2,3]*8;
  1178.     END;
  1179.  
  1180.   cls (vaddr,0);
  1181.   cls (vga,0);
  1182.   Xoff := 160;
  1183.   Yoff:=100;
  1184.   zoff:=-500;
  1185.  
  1186.   ytopclip:=101;
  1187.   ybotclip:=100;
  1188.   line (0,100,319,100,100,vga);
  1189.   delay (2000);
  1190.   for loop1:=1 to 25 do BEGIN
  1191.     RotatePoints (deg,deg,deg);
  1192.     DrawPoints;
  1193.     line (0,ytopclip,319,ytopclip,100,vaddr);
  1194.     line (0,ybotclip,319,ybotclip,100,vaddr);
  1195.     flip (vaddr,vga);
  1196.     cls (vaddr,0);
  1197.     deg:=(deg+5) mod 360;
  1198.     ytopclip:=ytopclip-4;
  1199.     ybotclip:=ybotclip+4;
  1200.   END;
  1201.   Repeat
  1202.     if keypressed then ch:=upcase (Readkey);
  1203.     RotatePoints (deg,deg,deg);
  1204.     DrawPoints;
  1205.     line (0,0,319,0,100,vaddr);
  1206.     line (0,199,319,199,100,vaddr);
  1207.     flip (vaddr,vga);
  1208.     cls (vaddr,0);
  1209.     deg:=(deg+5) mod 360;
  1210.   Until ch=#27;
  1211.   for loop1:=1 to 25 do BEGIN
  1212.     ytopclip:=ytopclip+4;
  1213.     ybotclip:=ybotclip-4;
  1214.     RotatePoints (deg,deg,deg);
  1215.     DrawPoints;
  1216.     line (0,ytopclip,319,ytopclip,100,vaddr);
  1217.     line (0,ybotclip,319,ybotclip,100,vaddr);
  1218.     flip (vaddr,vga);
  1219.     cls (vaddr,0);
  1220.     deg:=(deg+5) mod 360;
  1221.   END;
  1222. END;
  1223.  
  1224.  
  1225. BEGIN
  1226.   clrscr;
  1227.   writeln ('Welcome to the fourteenth trainer! This one is on glenzing, and also');
  1228.   writeln ('throws in a faster poly, fixed point math and a lot more assembler.');
  1229.   writeln;
  1230.   Writeln ('This isn''t very interactive ... hit any key to start, and then');
  1231.   writeln ('hit the [ESC] key to exit. It is a glenzed cube spinning in the');
  1232.   writeln ('middle of the screen. Read the text file for more information on');
  1233.   writeln ('how the fixed point etc. works ... it will also help a lot if you');
  1234.   writeln ('compare it with TUTPROG9.PAS, as this is the same 3D system, just');
  1235.   writeln ('speeded up.');
  1236.   writeln;
  1237.   writeln;
  1238.   writeln;
  1239.   write ('Hit any key to continue ...');
  1240.   readkey;
  1241.   SetUpVirtual;
  1242.   SetMCGA;
  1243.   SetUpPoints;
  1244.   MoveAround;
  1245.   SetText;
  1246.   ShutDown;
  1247.   Writeln ('All done. This concludes the fourteenth sample program in the ASPHYXIA');
  1248.   Writeln ('Training series. You may reach DENTHOR under the names of GRANT');
  1249.   Writeln ('SMITH/DENTHOR/ASPHYXIA on the ASPHYXIA BBS.I also occasinally');
  1250.   Writeln ('RSAProg, comp.lang.pascal and comp.sys.ibm.pc.demos. E-mail me at :');
  1251.   Writeln ('    smith9@batis.bis.und.ac.za');
  1252.   Writeln ('The numbers are available in the main text. You may also write to me at:');
  1253.   Writeln ('             Grant Smith');
  1254.   Writeln ('             P.O. Box 270');
  1255.   Writeln ('             Kloof');
  1256.   Writeln ('             3640');
  1257.   Writeln ('             Natal');
  1258.   Writeln ('             South Africa');
  1259.   Writeln ('I hope to hear from you soon!');
  1260.   Writeln; Writeln;
  1261.   Write   ('Hit any key to exit ...');
  1262.   readkey;
  1263. END.
  1264.